import { GameOption } from "../../constant/GameOption";
import { GameStage } from "../../constant/GameState";
import { SocketGameEvent } from "../../constant/SocketGameEvent";
import { SocketRoomEvent } from "../../constant/SocketRoomEvent";
import CardCtrl from "../../controllers/CardCtrl";
import CardNumCtrl from "../../controllers/CardNumCtrl";
import LandCardsCtrl from "../../controllers/LandCardsCtrl";
import PlayerCtrl from "../../controllers/PlayerCtrl";
import TimerCtrl from "../../controllers/TimerCtrl";
import Player, { IPlayerVo } from "../../data/Player";
import { IPokerVo, Poker } from "../../data/Poker";
import Room from "../../data/Room";
import { ISocketData } from "../../data/SocketData";
import { IUserVo } from "../../data/User";
import Store from "../../store/Store";
import PokerUtil from "../../utils/PokerUtil";
import NetMgr from "../NetManager/NetMgr";

const { ccclass, property } = cc._decorator;

/**
 * Room场景管理类
 * 会被挂载到Room场景根节点上
 * 管理该场景的逻辑
 */
@ccclass
export default class RoomMgr extends cc.Component {
    // 已出的牌父节点
    @property(cc.Node)
    cardsPlayedParent: cc.Node = null
    // 玩家手牌父节点，用于本机用户手牌预制体
    @property(cc.Node)
    handCardsParent: cc.Node = null

    // 顶部地主牌父节点，用于存放地主牌预制体
    @property(cc.Node)
    landCardsParent: cc.Node = null

    // 牌预制体
    @property(cc.Prefab)
    cardPrefab: cc.Prefab = null
    // 玩家预制体
    @property(cc.Prefab)
    playerPrefab: cc.Prefab = null
    // 地主牌预制体
    @property(cc.Prefab)
    landCardsPrefab: cc.Prefab = null

    // 定时器预制体
    @property(cc.Prefab)
    timerPrefab: cc.Prefab = null

    @property(cc.Prefab)
    cardNumPrefab: cc.Prefab = null

    // 房间号
    @property(cc.Label)
    roomTitleLabel: cc.Label = null

    // 座位
    @property(cc.Node)
    playerSeatParent: cc.Node = null

    // 操作按钮组合
    @property(cc.Node)
    prepareOptionNode: cc.Node = null    // 准备按钮
    @property(cc.Node)
    callOptionNode: cc.Node = null       // 叫地主按钮
    @property(cc.Node)
    playOptionNode: cc.Node = null        // 出牌按钮

    // 其他玩家的手牌数量
    @property(cc.Node)
    cardsNumParent: cc.Node = null

    // 定时器
    @property(cc.Node)
    timerParent: cc.Node = null

    // 已准备提示符
    @property(cc.Node)
    prepareTipsNode: cc.Node = null       // 已准备提示符

    // 叫地主/不叫地主提示符
    @property(cc.Node)
    callLandTipsNode: cc.Node = null

    // 阶段标签
    @property(cc.Label)
    stageLabel: cc.Label = null           // 游戏阶段

    // datas
    private localPlayerIndex: number = 0            // 本机玩家下标
    private room: Room = null
    private selectdCards: Set<Poker> = new Set()    //本机玩家选择的手牌
    private landLordId: string = ''                 // 地主玩家id
    private preCards: Array<Poker> = []             // 上一出牌
    private prePlayerId: string = ''                // 上一出牌的玩家id

    start() {
        // 初始化
        this.init()
    }

    init() {
        this.selectdCards = new Set()
        this.preCards = []
        this.landLordId = ''

        // 初始化房间数据
        this.initRoomData()

        // 监听网络信息
        this.initLiteningNet()

        // 默认显示准备按钮
        this.showOption(GameOption.PREPARE)
    }

    /**
     * 初始化房间数据
     */
    private initRoomData() {
        // 房间号
        this.roomTitleLabel.string = `房间号：${Store.room.id}`
        // 本机玩家入座一号位
        // this.setSeatPalyer(1, Store.user)
        // 本机玩家座位号
        this.localPlayerIndex = Store.index
        // 初始化房间其他玩家数据
        Store.room.players.forEach((player, index) => {
            if (player) {
                // 初始化座位信息
                const seatNum = this.getIndexToSeatNum(index)
                this.setSeatPlayer(seatNum, player)

                // 已经准备的信息
                if (player.prepare) {
                    this.showPrepareTip(seatNum)
                }
            }
        })
        // 房间对象
        this.room = new Room(Store.room)
    }

    /**
     * 初始化监听网络请求（Socket监听）
     */
    private initLiteningNet() {
        // 监听加入房间
        NetMgr.on(SocketRoomEvent.JOIN, this.onRoomJoin.bind(this))

        // 监听离开房间
        NetMgr.on(SocketRoomEvent.LEAVE, this.onRoomLeave.bind(this))

        // 监听游戏阶段变化
        NetMgr.on(SocketGameEvent.CHANGE_STAGE, this.onGameStage.bind(this))

        // 监听有人准备/取消准备
        NetMgr.on(SocketGameEvent.PREPARE, this.onPlayerPrepare.bind(this))

        // 监听系统发牌
        NetMgr.on(SocketGameEvent.DEAL, this.onDeal.bind(this))

        // 监听轮到谁叫地主
        NetMgr.on(SocketGameEvent.SET_CALL_PLAYER, this.onSetCallPlayer.bind(this))

        // 监听有人叫地主/不叫地主
        NetMgr.on(SocketGameEvent.CALL, this.onCall.bind(this))

        // 监听系统设置地主
        NetMgr.on(SocketGameEvent.SET_LANDLORD, this.onSetLandlord.bind(this))

        // 监听轮到谁出牌
        NetMgr.on(SocketGameEvent.SET_PLAY, this.onSetPlay.bind(this))

        // 监听有人出牌/不出牌
        NetMgr.on(SocketGameEvent.PLAY_CARDS, this.onPlayCards.bind(this))

        // 监听游戏结果/游戏结束
        NetMgr.on(SocketGameEvent.RESULT, this.onResult.bind(this))
    }

    // 按钮点击事件

    /**
     * 返回按钮点击事件
     */
    async onBackBtnClickEvent() {
        console.log('onBackBtnClickEvent')
        // 网络请求：离开房间
        await NetMgr.leaveRoom(Store.user.id, Store.room.id)
        // 返回大厅界面
        cc.director.loadScene('hall')
    }

    /**
     * 准备按钮点击事件
     */
    async onPrepareBtnClickEvent() {
        console.log('onPrepareBtnClickEvent')
        // 网络请求：准备
        await NetMgr.prepare(Store.user.id, Store.room.id, true)
        // 清空已出的牌
        this.clearCardsPlayed()
        // 关闭准备按钮
        this.offOption()
        // 显示准备提示符
        this.showPrepareTip(1)
        const index = this.room.getPlayerIndex(Store.user.id)
        if (index != -1) {
            this.room.players[index].prepare = true
        }
        // 全部准备，关闭准备提示符
        if (this.isAllPrepare()) {
            this.offPrepareTips()
        }

    }

    /**
     * 叫地主按钮点击事件
     */
    async onCallBtnClickEvent() {
        console.log('onCallBtnClickEvent')
        // 关闭按钮
        this.offOption()
        // 关闭定时器
        this.clearTimer()
        // 发送叫地主请求
        NetMgr.call(Store.user.id, Store.room.id, true)
    }

    /**
     * 不叫地主按钮点击事件
     */
    async onNoCallBtnClickEvent() {
        console.log('onNoCallBtnClickEvent')
        // 关闭按钮
        this.offOption()
        // 关闭定时器
        this.clearTimer()
        // 发送不叫地主请求
        NetMgr.call(Store.user.id, Store.room.id, false)
    }

    /**
     * 出牌按钮点击事件
     */
    async onPlayBtnClickEvent() {
        console.log('onPlayBtnClickEvent')
        // 获取选中的牌
        const cards: Array<Poker> = []
        for (const card of this.selectdCards.values()) {
            cards.push(card)
        }

        // 上一个出牌的玩家是自己，那么清空上一副牌
        if (this.prePlayerId == Store.user.id) {
            this.preCards = []
        }

        // 校验并且比较大小
        const result = PokerUtil.legal(this.preCards, cards)
        if (!result) {
            console.log('牌型比较失败或牌型错误，无法出牌')
            return
        }

        // 关闭出牌按钮
        this.offOption()
        // 关闭定时器
        this.clearTimer()
        // 删除屏幕上的手牌
        this.deletePoker(cards)
        // 屏幕显示出的牌
        this.showCardsPlayed(cards)

        // 【校验成功】
        // 发送给后端
        NetMgr.play(Store.user.id, Store.room.id, true, cards).catch(err => {
            console.log('出牌失败，服务器校验异常：', err)
        })
        // 清空选中的牌
        this.selectdCards.clear()
        // 记录出的牌
        this.preCards = cards
        this.prePlayerId = Store.user.id
    }

    /**
     * 不出牌按钮点击事件
     */
    async onNotPlayBtnClickEvent() {
        console.log('onNotPlayBtnClickEvent')
        // 关闭出牌按钮
        this.offOption()
        // 关闭定时器
        this.clearTimer()
        // 发送不出牌请求
        NetMgr.play(Store.user.id, Store.room.id, false)
    }

    /**
     * 显示玩家手牌
     */
    private showPoker(cards: Array<Poker>) {
        // 移除所有子节点
        this.handCardsParent.removeAllChildren()

        for (let i = 0; i < cards.length; i++) {
            const poker = cards[i]

            // 创建牌预制体
            let cardNode = cc.instantiate(this.cardPrefab)
            cardNode.scale = 0.8
            let cardCtrl = cardNode.getComponent(CardCtrl)
            this.handCardsParent.addChild(cardNode)

            cardCtrl.init(poker)    // 初始化牌预制体数据

            // 牌触摸事件
            cardCtrl.addTouchListener((poker: Poker, isSelected: boolean) => {
                console.log('isSelected:', isSelected)
                console.log('poker:', poker)
                // 保存已选择的牌
                if (this.selectdCards.has(poker)) {
                    this.selectdCards.delete(poker)
                } else {
                    this.selectdCards.add(poker)
                }
                console.log('selectdCards:', this.selectdCards)
            })
        }
    }

    /**
     * 清空玩家手牌
     */
    private clearPoker() {
        // 移除所有子节点
        this.handCardsParent.removeAllChildren()
    }

    /**
     * 删除玩家手牌
     * @param cards
     * @returns 删除的节点
     */
    private deletePoker(cards: Array<Poker>) {
        const handCardsNodeArr = this.handCardsParent.children
        cards.forEach(card => {
            for (let i = 0; i < handCardsNodeArr.length;) {
                const cardCtrl = handCardsNodeArr[i].getComponent(CardCtrl)
                if (cardCtrl.poker == card) {
                    // 删除
                    this.handCardsParent.removeChild(handCardsNodeArr[i])
                } else {
                    i++
                }
            }
        })
    }

    /**
     * 设置座位的玩家信息
     * @param seatNum 座位号 1-3
     * @param user 
     */
    private setSeatPlayer(seatNum: number, user: IUserVo) {
        if (!(seatNum >= 1 && seatNum <= 3)) {
            throw new Error(`seatNum:${seatNum}. seatNum not 1~3. `)
        }

        let SeatNode: cc.Node = this.playerSeatParent.children[seatNum - 1]

        // 创建玩家节点
        const player = cc.instantiate(this.playerPrefab)
        player.name = 'player'
        // 节点挂载到指定座位上
        SeatNode.removeAllChildren()
        SeatNode.addChild(player)
        const playerCtrl = player.getComponent(PlayerCtrl)
        console.log('playerCtrl:', playerCtrl)
        // 初始化玩家节点数据
        playerCtrl.init(user)
    }

    /**
     * 获取对应座位的玩家
     * @param seatNum 
     */
    private getSeatPlayerNode(seatNum: number): cc.Node | undefined {
        let seatNode: cc.Node = this.playerSeatParent.children[seatNum - 1]
        const playerNode = seatNode.getChildByName('player')
        if (playerNode) {
            return playerNode
        }
    }

    private clearSeatPalyer(seatNum: number) {
        if (!(seatNum >= 1 && seatNum <= 3)) {
            throw new Error(`seatNum:${seatNum}. seatNum not 1~3. `)
        }

        let SeatNode: cc.Node = this.playerSeatParent.children[seatNum - 1]

        SeatNode.removeAllChildren()
    }

    /**
     * 玩家下标转座位号（本机玩家肯定坐在本机界面的1号座位上）
     * @param index 玩家在房间中的下标
     */
    private getIndexToSeatNum(index: number): number {
        if (!(index >= 0 && index <= 2)) {
            throw new Error(`index:${index}, index not 0~2`)
        }
        if (this.localPlayerIndex == index) {
            return 1
        } else if (this.localPlayerIndex < index) {
            // x t t
            // _ x t
            if (index - this.localPlayerIndex == 1) {
                return 3
            } else if (index - this.localPlayerIndex == 2) {
                return 2
            }
        } else {
            this.localPlayerIndex > index
            // t x _
            // t t x
            if (this.localPlayerIndex - index == 1) {
                return 2
            } else if (this.localPlayerIndex - index == 2) {
                return 3
            }
        }
        return -1
    }

    /**
     * 显示某组操作按钮（准备、叫地主、出牌等按钮）
     * @param option 
     */
    private showOption(option: GameOption) {
        this.offOption()    // 关闭所有操作按钮
        switch (option) {
            // 显示准备操作
            case GameOption.PREPARE:
                this.prepareOptionNode.active = true
                break;
            // 显示叫地主操作按钮
            case GameOption.CALL:
                this.callOptionNode.active = true
                break;
            // 显示出牌操作按钮
            case GameOption.PALY:
                this.playOptionNode.active = true
                break;
            default:
                break;
        }
    }

    // 关闭所有操作
    private offOption() {
        this.playOptionNode.active = false
        this.callOptionNode.active = false
        this.prepareOptionNode.active = false
    }

    /**
     * 显示准备提示符
     * @param seatNum 
     */
    private showPrepareTip(seatNum: number) {
        if (!(seatNum >= 1 && seatNum <= 3)) {
            throw new Error(`seatNum:${seatNum}. seatNum not 1~3. `)
        }
        // this.prepareTipsNode.active = true
        this.prepareTipsNode.children[seatNum - 1].active = true
    }

    /**
     * 关闭所有准备提示符
     */
    private offPrepareTips() {
        this.prepareTipsNode.children.forEach(child => {
            child.active = false
        })
        // this.prepareTipsNode.active = false
    }

    /**
     * 显示地主牌
     * @param cards 
     */
    private showLandCards(cards?: [IPokerVo, IPokerVo, IPokerVo]) {
        this.landCardsParent.removeAllChildren()
        // 创建地主牌节点
        const landCardsNode = cc.instantiate(this.landCardsPrefab)
        this.landCardsParent.addChild(landCardsNode)
        if (cards) {
            // 初始化地主牌数据
            const landCardsCtrl = landCardsNode.getComponent(LandCardsCtrl)
            landCardsCtrl.init(cards)
        }
    }

    private closeLandCards() {
        this.landCardsParent.removeAllChildren()
    }

    /**
     * 显示叫地主提示符
     * @param seatNum 
     */
    private showCallLandTip(seatNum: number, isCall: boolean) {
        if (!(seatNum >= 1 && seatNum <= 3)) {
            throw new Error(`seatNum:${seatNum}. seatNum not 1~3. `)
        }

        // 确定显示的位置
        this.callLandTipsNode.active = true
        this.callLandTipsNode.children[seatNum - 1].removeAllChildren()

        // 显示图标
        if (isCall) {
            cc.resources.load('ui/callLand', cc.SpriteFrame, (err, asset) => {
                const slotSprite = this.callLandTipsNode.children[seatNum - 1].getComponent(cc.Sprite)
                slotSprite.spriteFrame = asset
            })
        } else {
            cc.resources.load('ui/notCallLand', cc.SpriteFrame, (err, asset) => {
                const slotSprite = this.callLandTipsNode.children[seatNum - 1].getComponent(cc.Sprite)
                slotSprite.spriteFrame = asset
            })
        }
    }

    private offCallLandTip(seatNum: number) {
        const slotSprite = this.callLandTipsNode.children[seatNum - 1].getComponent(cc.Sprite)
        slotSprite.spriteFrame = null
    }

    private clearCallLandTips() {
        this.callLandTipsNode.active = false
        this.callLandTipsNode.children.forEach(child => {
            child.getComponent(cc.Sprite).spriteFrame = null
        })
    }

    /**
     * 给地主玩家显示地主符号
     * @param saetNum 
     */
    private showLandLordImg(saetNum: number) {
        const playerNode = this.getSeatPlayerNode(saetNum)
        if (playerNode) {
            const playerCtrl = playerNode.getComponent(PlayerCtrl)
            playerCtrl.setLand(true)
        }
    }

    /**
     * 清空地主玩家显示的地主符号
     */
    private clearLandLordImg() {
        this.playerSeatParent.children.forEach(child => {
            if (child.children[0]) {
                const playerCtrl = child.children[0].getComponent(PlayerCtrl)
                playerCtrl.setLand(false)
            }
        })
    }

    /**
     * 显示出的牌
     * @param cards 
     */
    private showCardsPlayed(cards: Array<Poker>) {
        this.clearCardsPlayed()
        cards.forEach(card => {
            const cardNode = cc.instantiate(this.cardPrefab)
            const cardCtrl = cardNode.getComponent(CardCtrl)
            cardCtrl.init(card)
            cardCtrl.touchEnable = false
            cardNode.scale = 0.7
            this.cardsPlayedParent.addChild(cardNode)
        })
    }

    /**
     * 清除显示出的牌
     */
    private clearCardsPlayed() {
        this.cardsPlayedParent.removeAllChildren()
    }

    /**
     * 显示定时器
     * @param seatNum 
     */
    private showTimer(callback: Function, seatNum: number, delay: number = 20) {
        if (!(seatNum >= 1 && seatNum <= 3)) {
            throw new Error(`seatNum:${seatNum}. seatNum not 1~3. `)
        }

        // 创建定时器节点
        const timerNode = cc.instantiate(this.timerPrefab)
        const timerCtrl = timerNode.getComponent(TimerCtrl)

        // 添加节点
        const seatNode = this.timerParent.children[seatNum - 1]
        seatNode.removeAllChildren()
        seatNode.addChild(timerNode)

        // 初始化定时器
        timerCtrl.init(delay)
        timerCtrl.addTimeoutListener(callback)
    }

    /**
     * 关闭指定位置的定时器
     * @param seatNum 
     */
    private offTimer(seatNum: number) {
        this.timerParent.children[seatNum - 1].children.forEach(node => {
            node.destroy()
        })
    }

    /**
     * 清空所有定时器
     */
    private clearTimer() {
        this.timerParent.children.forEach(seatNode => {
            seatNode.children.forEach(node => {
                node.destroy()
            })
        })
    }

    /**
     * 显示其他玩家剩余的牌
     */
    private showOtherCardNum() {
        this.offOtherCardNum()
        // 0号下标对应座位1，座位1是本机玩家，不需要在本机显示剩余牌数
        for (let i = 1; i < this.cardsNumParent.children.length; i++) {
            let seatNode = this.cardsNumParent.children[i]
            const cardNumNode = cc.instantiate(this.cardNumPrefab)
            seatNode.addChild(cardNumNode)
            const cardNumCtrl = cardNumNode.getComponent(CardNumCtrl)
            cardNumCtrl.init(17)    // 默认每人17张牌
        }
    }

    /**
     * 关闭其他玩家剩余的手牌
     */
    private offOtherCardNum() {
        for (let i = 1; i < this.cardsNumParent.children.length; i++) {
            let seatNode = this.cardsNumParent.children[i]
            seatNode.removeAllChildren()
        }
    }

    /**
     * 增加其他玩家的剩余手牌
     * @param seatNum 
     * @param num 
     */
    private incOtherCardNum(seatNum: number, num: number) {
        if (!(seatNum >= 2 && seatNum <= 3)) {
            throw new Error(`seatNum:${seatNum}. seatNum not 2~3. `)
        }

        let seatNode = this.cardsNumParent.children[seatNum - 1]
        const cardNumCtrl = seatNode.children[0].getComponent(CardNumCtrl)
        cardNumCtrl.increaseNum(num)
    }

    /**
     * 减少其他玩家的剩余手牌
     * @param seatNum 座位号
     * @param num 
     */
    private subOtherCardNum(seatNum: number, num: number) {
        if (!(seatNum >= 2 && seatNum <= 3)) {
            throw new Error(`seatNum:${seatNum}. seatNum not 2~3. `)
        }

        let seatNode = this.cardsNumParent.children[seatNum - 1]
        const cardNumCtrl = seatNode.children[0].getComponent(CardNumCtrl)
        cardNumCtrl.subtractNum(num)
    }



    // 监听网络事件

    /**
     * 进入房间事件
     */
    private onRoomJoin(socketData: ISocketData) {
        console.log('[加入房间]:', socketData)
        const data: { index: number, player: IPlayerVo } = socketData.data
        const seatNum = this.getIndexToSeatNum(data.index)
        // 设置玩家信息
        this.setSeatPlayer(seatNum, data.player)
        // 保存玩家数据
        this.room.players[data.index] = new Player(data.player)
    }

    /**
     * 离开房间事件
     * @param socketData 
     */
    private onRoomLeave(socketData: ISocketData) {
        console.log('[离开房间]:', socketData)
        const data: { index: number, uid: string } = socketData.data
        const seatNum = this.getIndexToSeatNum(data.index)
        // 清除玩家信息
        this.clearSeatPalyer(seatNum)
        // 删除玩家数据
        this.room.players[data.index] = undefined
    }

    /**
     * 监听游戏状态变化事件
     * @param socketData 
     */
    private onGameStage(socketData: ISocketData) {
        console.log('[游戏阶段状态变化]:', socketData)
        const data: { stage: number } = socketData.data
        switch (data.stage) {
            // 准备阶段
            case GameStage.PREPARATION:
                this.showOption(GameOption.PREPARE) // 显示准备按钮组合
                this.clearLandLordImg()             // 清空地主图标
                this.clearTimer()                   // 清空定时器
                this.preCards = []
                this.stageLabel.string = '阶段：' + '准备阶段'
                break;
            // 发牌阶段
            case GameStage.REFEREE_DEAL:
                this.offPrepareTips()       // 关闭准备提示
                this.clearCallLandTips()    // 关闭所有叫地主提示（因为可能没人叫地主二导致重新发牌）
                this.stageLabel.string = '阶段：' + '发牌阶段'
                this.showLandCards()    // 显示地主牌控件
                this.showOtherCardNum() // 显示其他玩家剩余手牌
                break;
            // 叫地主阶段
            case GameStage.CALL_LANDLORD:
                this.offOption()    // 关闭所有操作按钮
                this.clearTimer()
                this.stageLabel.string = '阶段：' + '叫地主阶段'
                break;
            // 游戏阶段（出牌阶段）
            case GameStage.GAMING:
                //setTimeout(this.clearCallLandTips.bind(this), 500)// 清空叫地主提示符
                this.clearCallLandTips()
                this.stageLabel.string = '阶段：' + '出牌阶段'
                break;
            // 结算阶段
            case GameStage.SETTLEMENT:
                this.closeLandCards()
                this.clearPoker()
                this.clearAllPrepare()
                this.offOtherCardNum()
                // 清空玩家手牌
                this.stageLabel.string = '阶段：' + '结算阶段'
                break;
        }
    }

    /**
     * 监听玩家准备
     * @param socketData 
     */
    private onPlayerPrepare(socketData: ISocketData) {
        console.log('[准备]:', socketData)
        const data: { uid: string, isPrepare: boolean } = socketData.data
        const index = this.room.getPlayerIndex(data.uid)
        if (index == -1) {
            return
        }
        this.room.players[index].prepare = true // 设为准备
        // 显示准备
        const seatNum = this.getIndexToSeatNum(index)
        this.showPrepareTip(seatNum)
    }

    /**
     * 监听系统发牌
     * @param socketData 
     * @returns 
     */
    private onDeal(socketData: ISocketData) {
        console.log('[系统发牌]:', socketData)
        const data: { uid: string, cards: Array<IPokerVo> } = socketData.data

        const index = this.room.getPlayerIndex(data.uid)
        if (index == -1) {
            return
        }

        // 数据对象转为扑克对象
        const cards = data.cards.map(card => new Poker(card))

        // 排序
        PokerUtil.sort(cards)

        this.room.players[index].cards = cards

        if (Store.user.id === data.uid) {
            // 显示玩家手牌
            this.showPoker(cards)
        }

        // 告诉服务器，客户端准备就绪
        NetMgr.ready(Store.user.id, Store.room.id)
    }

    /**
     * 监听轮到谁叫地主
     * @param socketData 
     */
    private onSetCallPlayer(socketData: ISocketData) {
        console.log('[轮到谁叫地主]:', socketData)
        const data: { uid: string } = socketData.data

        const index = this.room.getPlayerIndex(data.uid)
        const seatNum = this.getIndexToSeatNum(index)

        this.clearTimer()
        if (data.uid === Store.user.id) {
            // 关闭当前玩家的叫地主提示符
            this.offCallLandTip(seatNum)
            // 显示叫地主按钮
            this.showOption(GameOption.CALL)
            // 显示定时器
            this.showTimer(() => {
                console.log('timer out')
                // 超时则不叫地主
                this.onNoCallBtnClickEvent()
            }, seatNum)
        } else {
            // 显示定时器
            this.showTimer(null, seatNum)
        }
    }

    /**
     * 监听谁叫了地主/不叫地主
     * @param socketData 
     */
    private onCall(socketData: ISocketData) {
        console.log('[有人叫地主/不叫地主]:', socketData)
        const data: { uid: string, isCall: boolean } = socketData.data
        // 获取用户下标
        const index = this.room.getPlayerIndex(data.uid)
        // 根据下标获取用户座位号
        const seatNum = this.getIndexToSeatNum(index)
        // 显示叫地主提示符
        this.showCallLandTip(seatNum, data.isCall)
    }

    /**
     * 监听系统设置地主
     * @param socketData 
     */
    private onSetLandlord(socketData: ISocketData) {
        this.clearCallLandTips() // 清空叫地主提示符

        console.log('[系统设置地主]:', socketData)
        const data: { uid: string, cards: [IPokerVo, IPokerVo, IPokerVo] } = socketData.data
        this.landLordId = data.uid      // 记录地主id
        PokerUtil.sort(data.cards)
        this.showLandCards(data.cards)  // 显示地主牌
        // 地主玩家显示地主图标
        const index = this.room.getPlayerIndex(data.uid)
        const seatNum = this.getIndexToSeatNum(index)
        this.showLandLordImg(seatNum)
        // 添加地主牌
        if (data.uid === Store.user.id) {
            // 本机玩家添加地主牌
            const player = this.room.players[index]
            const landCards = data.cards.map(card => new Poker(card))
            player.cards.push(...landCards)
            PokerUtil.sort(player.cards)
            this.showPoker(player.cards)
        } else {
            this.incOtherCardNum(seatNum, 3)   // 加3张地主牌
        }
    }

    /**
     * 监听轮到谁出牌
     * @param socketData 
     */
    private onSetPlay(socketData: ISocketData) {
        console.log('[轮到谁出牌]:', socketData)
        const data: { uid: string } = socketData.data

        if (this.prePlayerId == data.uid) {
            // 上一次出牌的也是这名玩家，那么清空上次的牌（因为当前不需要和自己比较）
            this.clearCardsPlayed()
        }

        const index = this.room.getPlayerIndex(data.uid)
        const seatNum = this.getIndexToSeatNum(index)

        this.clearTimer()
        if (Store.user.id === data.uid) {
            // 本机玩家出牌
            this.showOption(GameOption.PALY)    // 显示出牌按钮

            // 显示定时器
            this.showTimer(() => {
                // 超时则不出牌
                this.onNotPlayBtnClickEvent()
            }, seatNum)
        } else {
            // 其他玩家出牌
            // 显示定时器
            this.showTimer(null, seatNum)
        }
    }

    /**
     * 监听有玩家出牌/不出牌（不会包含本机玩家）
     */
    private onPlayCards(socketData: ISocketData) {
        console.log('[有玩家出牌/不出牌]:', socketData)
        const data: { uid: string, isPlay: boolean, cards: Array<IPokerVo> } = socketData.data
        if (data.isPlay) {
            // 记录出牌的玩家信息
            this.prePlayerId = data.uid
            this.preCards = data.cards.map(card => new Poker(card))
            // 该玩家出牌
            // 显示出牌
            const cards = data.cards.map(card => new Poker(card))
            this.showCardsPlayed(cards)

            // 减去该玩家的剩余手牌
            const index = this.room.getPlayerIndex(data.uid)
            const seatNum = this.getIndexToSeatNum(index)
            console.log('index:', index, 'seatNum:', seatNum)
            this.subOtherCardNum(seatNum, cards.length)
        } else {
            // 该玩家不出
            // TODO: 显示不出牌提示符
        }
    }

    private onResult(socketData: ISocketData) {
        console.log('[游戏结果]:', socketData)
        const data: { uid: string, isPlay: boolean, cards: Array<IPokerVo> } = socketData.data
    }
    /**
     * 是否全部准备
     * @returns 
     */
    private isAllPrepare(): boolean {
        let prepareCount = 0
        this.room.players.forEach(player => {
            if (player) {
                if (player.prepare) {
                    prepareCount++
                }
            }
        })
        return prepareCount == this.room.maxOfPeople
    }

    private clearAllPrepare() {
        this.room.players.forEach(player => {
            if (player) {
                player.prepare = false
            }
        })
    }

    onDestroy() {
        // 关闭监听加入房间
        NetMgr.off(SocketRoomEvent.JOIN)

        // 关闭监听离开房间
        NetMgr.off(SocketRoomEvent.LEAVE)

        // 关闭监听游戏阶段变化
        NetMgr.off(SocketGameEvent.CHANGE_STAGE)

        // 关闭监听有人准备/取消准备
        NetMgr.off(SocketGameEvent.PREPARE)

        // 关闭监听系统发牌
        NetMgr.off(SocketGameEvent.DEAL)

        // 关闭监听轮到谁叫地主
        NetMgr.off(SocketGameEvent.SET_CALL_PLAYER)

        // 关闭监听有人叫地主/不叫地主
        NetMgr.off(SocketGameEvent.CALL)

        // 关闭监听系统设置地主
        NetMgr.off(SocketGameEvent.SET_LANDLORD)

        // 关闭监听轮到谁出牌
        NetMgr.off(SocketGameEvent.SET_PLAY)

        // 关闭监听有人出牌/不出牌
        NetMgr.off(SocketGameEvent.PLAY_CARDS)
    }
}
